Skip to content

fix(create-database): server-driven dialog (#927)#953

Merged
datlechin merged 8 commits intomainfrom
fix/createdb-server-driven
Apr 30, 2026
Merged

fix(create-database): server-driven dialog (#927)#953
datlechin merged 8 commits intomainfrom
fix/createdb-server-driven

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

Rewrite the Create Database flow to be server-driven instead of using a hardcoded macOS-flavored locale list.

Closes #927. The reported failure (new collation (en_US.UTF-8) is incompatible with the collation of the template database (en_US.utf8)) was the visible symptom of an architectural smell across four engines:

  • PostgreSQL: hardcoded en_US.UTF-8 always sent as LC_COLLATE, no LC_CTYPE, never used TEMPLATE template0 (the workaround Postgres itself recommends in the error HINT).
  • Redshift: copy-pasted from PG, emitted LC_COLLATE which is invalid Redshift grammar. Real Redshift uses COLLATE { CASE_SENSITIVE | CASE_INSENSITIVE }. Any user hitting Create Database on a real cluster was failing.
  • MySQL/MariaDB: hardcoded charset list, hardcoded collation rules, no server discovery.

Architecture

The dialog is now driver-driven. Plugin kit gains PluginCreateDatabaseFormSpec describing fields (with options, defaults, visibility, grouping) discovered from the server. The view renders generically. The driver builds the SQL from a typed request struct.

  • New protocol: createDatabaseFormSpec() + createDatabase(_ request:) replace the old (name, charset, collation) shape.
  • PluginKit ABI bumped 7 → 8 across all 21 plugin Info.plists.
  • CreateDatabaseOptions.swift deleted (hardcoded list).
  • 6 other drivers (ClickHouse, MongoDB, MSSQL, Cassandra, BigQuery, CloudflareD1) migrated to the new shape, keeping existing behavior.
  • 4 drivers (DuckDB, LibSQL, SQLite, Redis) had the orphaned method removed; the dialog's Create button now hides automatically for engines that return no spec.

PostgreSQL specifics

  • Encodings, collations, and template1.datcollate/datctype discovered from pg_collation and pg_database.
  • LC_CTYPE mirrors LC_COLLATE (one picker, two clauses).
  • TEMPLATE template0 appended automatically when the chosen libc collation differs from template1.datcollate. Hard error if template1 lookup fails (no silent fallback to the original bug).
  • ICU provider added for PG 15+ (provider picker + ICU locale picker from pg_collation).
  • Version-aware SQL emission: PG 16+ uses unified LOCALE, PG 15 uses ICU_LOCALE with LC_COLLATE 'C' LC_CTYPE 'C'.
  • Server version parser handles "16.2", "160002" (libpq numeric), and "PostgreSQL 16.2 on x86_64...".
  • pg_collation filter includes 'b' (built-in) so C and POSIX show up on PG 16+.

MySQL/MariaDB

  • Charsets and collations discovered from information_schema.character_sets/collations.
  • Server defaults from SHOW VARIABLES.
  • Collation list filters by selected charset (driver declares groupedBy: "charset").
  • View resets stale collation when charset changes.
  • fetchSessionVariable typed to a SessionVariable enum (no string-interpolation injection vector).

Redshift

  • Single COLLATE picker (CASE_SENSITIVE / CASE_INSENSITIVE).
  • Emits correct Redshift grammar.

Files

47 files changed, +943/−298. New: PluginCreateDatabaseFormSpec.swift, CreateDatabaseFormSpec.swift, MySQLPluginDriver+CreateDatabase.swift. Deleted: CreateDatabaseOptions.swift.

Supersedes #950 (a 2-line tactical patch that hardcoded template0 for one path).

Test plan

  • macOS native Postgres (template1 = en_US.UTF-8): default selection works, create succeeds.
  • Docker postgres:16 on Linux (template1 = en_US.utf8): reproduces can not create new postgresql database #927 on main; with this PR, server default works and a different collation auto-uses TEMPLATE template0.
  • Postgres 15: provider picker visible, ICU emits ICU_LOCALE.
  • Postgres 16+: provider picker visible, ICU emits unified LOCALE.
  • Redshift: only COLLATE picker shown; emits COLLATE CASE_SENSITIVE / CASE_INSENSITIVE.
  • MySQL 8 / MariaDB: charset and collation populated from information_schema; default = server default; charset change clears stale collation.
  • SQLite / DuckDB / LibSQL / Redis: Create button hidden in switcher.
  • xcodebuild -project TablePro.xcodeproj -scheme TablePro -configuration Debug build -skipPackagePluginValidation passes.
  • swiftlint lint --strict passes.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d27c3f6388

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +947 to +948
if templateDefaults.collate != collation {
sql += " TEMPLATE template0"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Append template0 when LC_CTYPE differs from template1

In the libc path you set both LC_COLLATE and LC_CTYPE to the selected collation, but you only add TEMPLATE template0 when template1.datcollate differs. On clusters where template1.datctype is different (valid when initdb configured them separately), this branch omits template0 even though LC_CTYPE no longer matches template1, and PostgreSQL rejects CREATE DATABASE with a template locale mismatch. The template decision should account for both datcollate and datctype.

Useful? React with 👍 / 👎.


private func resetGroupedFields(after sourceId: String, in spec: CreateDatabaseFormSpec) {
for field in spec.fields where field.groupedBy == sourceId {
values[field.id] = defaultValue(from: field.kind) ?? ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset grouped fields to a value valid for the new group

When a source field changes, dependent grouped fields are reset to their static defaultValue, which can belong to the previous group (for example, MySQL collation default tied to the initial/server charset). That leaves the dependent picker with a stale selection outside the newly filtered options, and submission sends an invalid collation/charset pair that the driver rejects. The reset logic should pick a value from the current group (or clear it) instead of reusing the global default.

Useful? React with 👍 / 👎.

@datlechin datlechin force-pushed the fix/createdb-server-driven branch from 9ff44ff to b322169 Compare April 30, 2026 13:32
@datlechin datlechin merged commit 26766e9 into main Apr 30, 2026
2 checks passed
@datlechin datlechin deleted the fix/createdb-server-driven branch April 30, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

can not create new postgresql database

1 participant